#pragma once
#include <iostream>
#include <memory>
#include <string>
#include <sys/types.h>        
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>     // memeset
#include <functional>
#include <unordered_map>
#include "log.hpp"
using namespace std;

const string defaultIP = "0.0.0.0";    // 默认IP地址
const uint16_t defaultPort = 8080;     // 默认端口号
const size_t BUFFERSIZE = 1024;
Log log;    // 方便打日志
using func_t = function<string(const string &)>;                                      // 用于简单的cs模型
using func_t2 = function<string(const string &, const string&, const uint16_t&)>;     // 用于网络聊天室

enum {
    SOCKET_ERR,
    BIND_ERR,
};

class UdpServer
{
public:
    UdpServer(const uint16_t& port, const string& ip);
    ~UdpServer();
    void init();
    void run(func_t fun);
    void run2(func_t2 fun);
    void run3();
    void userOperation(const sockaddr_in& client, const string& ip, const uint16_t& port);
    void broadcastUser(const string& info, const string& ip, const uint16_t& port);
private:
    int _socketFd = 0;      
    string _ip;
    uint16_t _port;                                     // 服务器进程端口号
    bool _isRunning = false;    
    unordered_map<string, sockaddr_in> _onlineUser;     // 当前在线的用户
};

UdpServer::UdpServer(const uint16_t& port = defaultPort, const string& ip = defaultIP) : _port(port), _ip(ip)
{}

UdpServer::~UdpServer()
{
    close(_socketFd);
}

void UdpServer::init()
{
    /* 1.创建udp套接字 */ 
    _socketFd = socket(AF_INET, SOCK_DGRAM, 0);        
    if(_socketFd == -1) {
        // 创建失败
        log(FATAL, "创建套接字失败, fd: %d, 原因: %s\n", _socketFd, strerror(errno));
        exit(SOCKET_ERR);
    }
    else {
        // 创建成功
        log(INFO, "创建套接字成功, fd: %d\n", _socketFd);
        /* 2. 将一个套接字绑定到一个特定的网络地址 */
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        in_addr_t ipTmp = inet_addr(_ip.c_str());
        // sin_addr是个in_addr结构体类型的变量，里面有个in_addr_t类型的成员s_addr
        // local.sin_addr.s_addr = htons(INADDR_ANY);
        local.sin_addr.s_addr = ipTmp;
        // 开始绑定
        if(bind(_socketFd, (const struct sockaddr*)&local, sizeof(local)) == -1) {
            log(FATAL, "绑定失败, 原因: %s\n", strerror(errno));
            exit(BIND_ERR);
        }
        log(INFO, "绑定成功\n");
    }
}

void UdpServer::run(func_t fun)
{
    /* 从客户端拿数据，处理后再发给客户端 */
    _isRunning = true;
    while (_isRunning) {
        // 接受数据
        sockaddr_in client;
        socklen_t len = sizeof(client);  
        string inBuffer(BUFFERSIZE, 0);
        if(recvfrom(_socketFd, (char*)inBuffer.c_str(), inBuffer.size(), 0, (sockaddr*)&client, &len) == -1) {
            log(WARNING, "接受数据失败, 原因: %s\n", strerror(errno));
        }
        
        string echoString = fun(inBuffer);      // 回调fun函数
        // 发回客户端
        if (sendto(_socketFd, echoString.c_str(), echoString.size(), 0, (const sockaddr*)&client, len) == -1) {
            log(WARNING, "发送数据失败, 原因: %s\n", strerror(errno));
        }
    }
}

void UdpServer::run2(func_t2 fun)
{
    /* 从客户端拿数据，处理后再发给客户端 */
    _isRunning = true;
    while (_isRunning) {
        // 接受数据
        sockaddr_in client;
        socklen_t len = sizeof(client);  
        string inBuffer(BUFFERSIZE, 0);
        if(recvfrom(_socketFd, (char*)inBuffer.c_str(), inBuffer.size(), 0, (sockaddr*)&client, &len) == -1) {
            log(WARNING, "接受数据失败, 原因: %s\n", strerror(errno));
        }
         // 拿到客户端的ip和端口号
        string ip = inet_ntoa(client.sin_addr);
        uint16_t cPort = client.sin_port;
        string echoString = fun(inBuffer, ip, cPort);      // 回调fun函数, 这里有ip和端口号, 方便标识
        // 发回客户端
        if (sendto(_socketFd, echoString.c_str(), echoString.size(), 0, (const sockaddr*)&client, len) == -1) {
            log(WARNING, "发送数据失败, 原因: %s\n", strerror(errno));
        }
    }
}

void UdpServer::userOperation(const sockaddr_in& client, const string& ip, const uint16_t& port)
{
    /* 如果该用户不在哈希表中，就插入 */
    if(_onlineUser.count(ip) == true)   return;
    _onlineUser.insert({ip, client});
     cout << "用户[" << ip << ' ' << port << "]# 已经添加到用户列表中!"  << endl;
}

void UdpServer::broadcastUser(const string&info, const string& ip, const uint16_t& port)
{
    /* 让所有的在线用户看到同一份消息 */
    for(const auto& e : _onlineUser) {
        string echoString;
        echoString += "[";
        echoString += ip;
        echoString += " ";
        echoString += to_string(port);
        echoString += "]# ";
        echoString += info;
        // 发回客户端
        socklen_t len = sizeof(e.second);
        if (sendto(_socketFd, echoString.c_str(), echoString.size(), 0, (sockaddr*)(&e.second), len) == -1) {
            log(WARNING, "发送数据失败, 原因: %s\n", strerror(errno));
        } else {
            cout << "客户端发送给:" << e.first <<"消息成功" << endl;
        }
    }
}

void UdpServer::run3()
{
    /* 网络聊天室,从客户端拿到信息,转发给所有人 */
    _isRunning = true;
    while (_isRunning) {
        // 接受数据
        sockaddr_in client;
        socklen_t len = sizeof(client);  
        string inBuffer(BUFFERSIZE, 0);
        if(recvfrom(_socketFd, (char*)inBuffer.c_str(), inBuffer.size(), 0, (sockaddr*)&client, &len) == -1) {
            log(WARNING, "接受数据失败, 原因: %s\n", strerror(errno));
            continue;
        }
        // 拿到客户端的ip和端口号
        string ip = inet_ntoa(client.sin_addr);
        uint16_t cPort = ntohs(client.sin_port);
        // 如果该用户不在哈希表中，就插入
        userOperation(client, ip, cPort);
        // 服务端打印一下
        cout << "用户[" << ip << ' ' << cPort << "]说# " << inBuffer << endl;
        // 让所有的在线用户看到同一份消息
        broadcastUser(inBuffer, ip, cPort);
    }
}

